package yolo

import (
	"appengine"
	"appengine/datastore"
	"appengine/user"
	"encoding/json"
	"fmt"
	"net/http"
	"regexp"
	"time"
)

const numItemsPerPage = 20

func init() {
	http.HandleFunc("/", topPage)

	http.HandleFunc("/yolo/yolo-token.js", getTokenJS)
	http.HandleFunc("/ajax/tree_status", getTreeStatus)
	http.HandleFunc("/ajax/get_user_data", getUserData)
	http.HandleFunc("/ajax/get_data", getPageData)
	http.HandleFunc("/ajax/get_issue", getIssueDetailData)
	http.HandleFunc("/ajax/get_file", getFileData)

	// TODO: move them to admin/poller
	http.HandleFunc("/admin/buildbot_start_check", postStartCheckBuildbot)
	http.HandleFunc("/worker/buildbot_start_check", startCheckBuildbot)
	http.HandleFunc("/buildbot_activity_data", getBuildbotActivityData)
	http.HandleFunc("/admin/google_codesite_start_check", postStartCheckGoogleCodesite)
	http.HandleFunc("/worker/google_codesite_start_check", startCheckGoogleCodesite)
	http.HandleFunc("/admin/chromium_status_start_check", postStartCheckChromiumStatus)
	http.HandleFunc("/worker/chromium_status_start_check", startCheckChromiumStatus)
	http.HandleFunc("/worker/chromium_status_store_action", storeChromiumStatusAction)
}

var validNameRegexp = regexp.MustCompile(`^[a-zA-Z0-9._%]+$`)
func topPage(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	if len(r.URL.Path) > 1 && r.URL.Path != "/favicon.ico" {
		name := r.URL.Path[1:]
		if validNameRegexp.MatchString(name) {
			name = name + "@chromium.org"
		}
		_, err := GetUser(c, name)
		if err == nil {
			http.Redirect(w, r, fmt.Sprintf("/ui/yolo.html#/?view=Activities&user=%s", name), http.StatusFound)
			return
		}
	}
	http.Redirect(w, r, "/ui/yolo.html", http.StatusFound)
	return
}

func getTreeStatus(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "", http.StatusMethodNotAllowed)
		return
	}
	c := appengine.NewContext(r)
	ch := make(chan error)

	var os, cs []Activity

	go func() {
		q := datastore.NewQuery("Activity").Order("-Time").Filter("Type =", "ChromiumTreeOpen").Limit(1)
		_, err := q.GetAll(c, &os)
		ch <- err
	}()

	go func() {
		q := datastore.NewQuery("Activity").Order("-Time").Filter("Type =", "ChromiumTreeClose").Limit(1)
		_, err := q.GetAll(c, &cs)
		ch <- err
	}()

	for i := 0; i < 2; i++ {
		err := <-ch
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}

	latestTime := time.Unix(0, 0)
	var latest *Activity
	for _, v := range os {
		if v.Time.After(latestTime) {
			latestTime = v.Time
			latest = &v
		}
	}
	for _, v := range cs {
		if v.Time.After(latestTime) {
			latestTime = v.Time
			latest = &v
		}
	}

	if latest == nil {
		latest = &Activity{}
	}
	w.Write([]byte(latest.Snippet))
}

func getTokenJS(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	u := user.Current(c)
	token := ""
	xsrf := ""
	if u != nil {
		token, _ = createChannelToken(c, u.Email)
	} else {
		token, _ = createChannelToken(c, "anonymous")
	}
	w.Header().Set("Content-Type", "text/javascript")
	fmt.Fprintf(w, "var yoloChannelToken = '" + token + "';");
	fmt.Fprintf(w, "var yoloXsrfToken = '" + xsrf + "';");
}

func returnJSONData(c appengine.Context, w http.ResponseWriter, message interface{}, err error) {
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	token, err := getXSRFToken(c)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	page := &PageJSONData{token, message}
	data, err := json.Marshal(page)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.Write(data)
}

type PageJSONData struct {
	XSRFToken string
	Data interface{}
}

type UserData struct {
	Email string
	LoginURL string
	LogoutURL string
	IsAdmin bool
	IsDevAppServer bool
}

func getUserData(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	u := user.Current(c)
	m := &UserData{}
	if u != nil {
		m.Email = u.Email
		m.LogoutURL, _ = user.LogoutURL(c, "/")
		m.IsAdmin = user.IsAdmin(c)
	} else {
		m.LoginURL, _ = user.LoginURL(c, r.FormValue("url"))
	}
	m.IsDevAppServer = appengine.IsDevAppServer()
	returnJSONData(c, w, m, nil)
}

func performCommonValidation(w http.ResponseWriter, r *http.Request) bool {
	if r.Method != "POST" {
		http.Error(w, "Invalid HTTP method", http.StatusMethodNotAllowed)
		return false
	}
	c := appengine.NewContext(r)
	xsrf := r.FormValue("xsrf")
	if !validateXSRFToken(c, xsrf) {
		http.Error(w, "XSRF mismatch", http.StatusBadRequest)
		return false
	}
	return true
}

type UserSummary struct {
	FileCommits []*CountMap
	RelatedUsers []*CountMap
}

type FileSummary struct {
	ActiveUsers []*CountMap
}

type PageDataRequest struct {
	User string
	View string
	Page int
}

type PageData struct {
	Request *PageDataRequest
	UserSummary *UserSummary
	SubHeader string
	Issues []Issue
	Activities []interface{}
	HasMore bool
}

func getPageDataSubheader(view, email string, u *user.User) string {
	switch view {
	case "Home":
		if email != "" {
			return "Timeline of " + email
		}
		return ""
	case "Issues":
		if u != nil && u.Email == email {
			return "My recent issues"
		} else if email != "" {
			return "Recent issues of " + email
		}
		return "Recent public issues"
	case "Activities":
		if u != nil && u.Email == email {
			return "My recent activities"
		} else if email != "" {
			return "Recent activities of " + email
		}
		return "Recent public activities"
	}
	return ""
}

func getPageData(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	if !performCommonValidation(w, r) {
		return
	}
	u := user.Current(c)
	email := r.FormValue("user")
	view := r.FormValue("view")
	page := getFormValueInt(r, "page", 1)
	request := &PageDataRequest{ email, view, page }

	pageSize := numItemsPerPage
	page = max(1, page)
	offset := (page - 1) * pageSize
	limit := pageSize + 1

	if view == "" {
		view = "Home"
	}
	if email == "" {
		if u != nil {
			email = u.Email
		}
	}
	if email == "ALL" {
		email = ""
	}
	if email == "" && view == "Home" {
		// No data to return.
		returnJSONData(c, w, []byte{}, nil)
		return
	}

	var issues []Issue
	switch view {
	case "Home":
		activities, err := GetTimeline(c, email, offset, limit)
		page := createPageData(c, pageSize, issues, activities)
		page.Request = request
		page.SubHeader = getPageDataSubheader(view, email, u)
		returnJSONData(c, w, page, err)
	case "Issues":
		err := GetIssues(c, email, offset, limit, &issues)
		page := createPageData(c, pageSize, issues, nil)
		page.Request = request
		page.SubHeader = getPageDataSubheader(view, email, u)
		returnJSONData(c, w, page, err)
	case "Activities":
		activities, err := GetActivities(c, email, "", offset, limit)
		page := createPageData(c, pageSize, issues, activities)
		page.Request = request
		page.SubHeader = getPageDataSubheader(view, email, u)
		page.UserSummary = getUserSummary(c, email)
		returnJSONData(c, w, page, err)
	default:
		http.Error(w, "Unknown view is requested.", http.StatusNotFound)
	}
}

func createPageData(c appengine.Context, pageSize int, issues []Issue, activities []interface{}) *PageData {
	page := &PageData{}
	if activities == nil {
		activities = []interface{}{}
	}
	page.Issues = issues
	if len(issues) > pageSize {
		page.HasMore = true
		page.Issues = issues[:pageSize]
	}
	page.Activities = activities
	if len(activities) > numItemsPerPage {
		page.HasMore = true
		page.Activities = activities[:pageSize]
	}
	return page
}

func getIssueDetailData(w http.ResponseWriter, r *http.Request) {
	if !performCommonValidation(w, r) {
		return
	}

	c := appengine.NewContext(r)
	id := r.FormValue("issue")
	if id == "" {
		http.Error(w, "issue must be specified.", http.StatusBadRequest)
		return
	}
	issue, err := GetIssueDetailDataById(c, id)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	returnJSONData(c, w, issue, err)
}

func getFileData(w http.ResponseWriter, r *http.Request) {
	if !performCommonValidation(w, r) {
		return
	}
	c := appengine.NewContext(r)
	path := r.FormValue("path")
	active_users, err := GetUserFileActivityCounts(c, path)
	if active_users != nil && len(active_users) > 10 {
		active_users = active_users[:10]
	}
	summary := &FileSummary{active_users}
	returnJSONData(c, w, summary, err)
}

func getUserSummary(c appengine.Context, email string) *UserSummary {
	ch := make(chan error)
	summary := &UserSummary{}

	go func() {
		results, err := GetUserActivityCounts(c, email)
		related_users := []*CountMap{}
		for _, u := range results {
			// TODO: list up exclude users somewhere
			if u.Key != email && u.Key != "buildbot@chromium.org" {
				related_users = append(related_users, u)
			}
			if len(related_users) > 5 {
				break
			}
		}
		summary.RelatedUsers = related_users
		ch <- err
	}()

	go func() {
		u := NewUser(email)
		files, err := u.GetFileCommitCounts(c)
		summary.FileCommits = files
		ch <- err
	}()

	for i := 0; i < 2; i++ {
		err := <-ch
		if err != nil {
			c.Errorf("%s: Got error: %s", currentLocation(), err.Error())
		}
	}

	return summary
}

func getXSRFToken(c appengine.Context) (string, error) {
	// TODO
	return "", nil
}

func validateXSRFToken(c appengine.Context, token string) bool {
	// TODO
	return true
}
